FullStackHero 10 .NET Starter Kit Release Merge#1152
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces the FullStackHero 10 .NET Starter Kit, implementing a modular monolith architecture with comprehensive modules for Identity, Multitenancy, and Auditing. The implementation includes a mediator-based CQRS pattern, JWT authentication with refresh tokens, role/permission-based authorization, background job support, caching abstractions, mailing services, and storage abstractions (local and S3). The Blazor client uses Shadcn-inspired MudBlazor wrappers with generated API clients via NSwag, while the infrastructure includes multi-app AWS scaffolding using Terraform and OpenTelemetry-based observability.
Key Changes
- Modular architecture with separate Identity, Multitenancy, and Auditing modules implementing contracts and handlers
- JWT authentication, role/permission system, and Finbuckle multitenancy with per-tenant provisioning lifecycle
- Auditing pipeline with request/response/security/exception tracking and background sink for SQL persistence
- OpenTelemetry integration, rate limiting, storage abstraction (local/S3), and comprehensive building blocks for caching, jobs, mailing, and persistence
Reviewed changes
Copilot reviewed 295 out of 1048 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| Directory.Packages.props | Updated package versions to .NET 10.0 and newer dependencies including Finbuckle 10.0.0, Mediator 3.1.0-preview.14, Hangfire 1.8.22, and OpenTelemetry 1.14.0 |
| Directory.Build.props | Enhanced with .NET 10.0 target, comprehensive code analysis settings, NuGet metadata, and stricter quality controls |
| BuildingBlocks/Web/*.cs | New Web building block with OpenAPI/Scalar integration, OpenTelemetry, Serilog logging, rate limiting, security headers, CORS, versioning, and module loading |
| BuildingBlocks/Storage/*.cs | Storage abstraction supporting local filesystem and AWS S3 with file type validation and upload/removal operations |
| BuildingBlocks/Shared/*.cs | Shared contracts for multitenancy (AppTenantInfo), identity (claims, permissions, roles), pagination, and database options |
| BuildingBlocks/Persistence/*.cs | Persistence infrastructure with specifications pattern, EF Core extensions, and database initialization interfaces |
| Modules/Identity/Modules.Identity.Contracts/*.cs | Identity module contracts including commands/queries for token generation, user management, role management, and associated DTOs |
| Modules/Auditing/Modules.Auditing.Contracts/*.cs | Auditing contracts with event types, payloads, DTOs, and interfaces for audit publishing, serialization, and sinking |
| Modules/Auditing/Modules.Auditing/*.cs | Auditing implementation with SQL sink, EF interceptor, HTTP middleware for request/response capture, channel-based publisher, and query handlers |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Wow! You woke up!! :) Excelent works, I going to clone and test it... Please check, you forgot push the /docs folders because is added in .gitignore: "/docs" Thanks in advanced. |
|
I am currently using VS2026. |
Perfect approach, check that may be some ideas are usefull: And this "spec driven AI design": |
|
@maxiar looks like its a member only story. any crucial takeaways? |
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
fullstackhero | 4b7a641 | May 24 2026, 08:07 AM |
30 tests covering DM DirectKey ordering, group-DM creator-as-admin, member add/remove rules, MarkRead watermark, slug normalization, message edit/delete author + moderator gates, reply-count bumps, and tombstone idempotency. Exposes Modules.Chat internals to Chat.Tests + Integration.Tests so internal helpers (IncrementReplyCount/DecrementReplyCount) can be exercised directly in unit tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ChatChannelsTests — happy paths, archive/restore roundtrip, find-or-create DM idempotency via DirectKey, add/remove member, auth gates, validation of self-DM rejection. ChatMessagesTests — send/edit/delete lifecycle, cursor pagination, thread replies + parent ReplyCount bump, top-level filter excludes thread replies, mark-read watermark drops UnreadCount to zero, 1-level thread depth enforcement, 404s and 400 for missing/invalid. Also corrects an outdated comment in ChatModule.cs — ListMyChannels maps GET /channels (not /channels/me). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a single shared AppHub at /api/v1/realtime/hub (BuildingBlocks/Web/Realtime)
with the Redis backplane wired when CachingOptions:Redis is set. The hub joins
each connection to user:{userId} and to channel:{channelId} for every channel the
user is a member of (looked up via the new IUserChannelLookup that the Chat module
implements against ChatDbContext).
Chat handlers now publish realtime events after SaveChanges:
SendMessage → ChatMessageCreated → channel:{id}
EditMessage → ChatMessageEdited → channel:{id}
DeleteMessage → ChatMessageDeleted → channel:{id}
AddMembers → ChatChannelMemberAdded → channel:{id}
+ ChatChannelAdded → user:{newMember}
RemoveMember → ChatChannelMemberRemoved → channel:{id}
+ ChatChannelRemoved → user:{removed}
MarkRead → ChatChannelRead → user:{caller} (cross-tab badge clear)
Also extends the JWT bearer OnMessageReceived path allow-list to accept
?access_token= for the hub path — browsers can't send Authorization headers on
WebSocket upgrade or EventSource fallback. Path-scoped to keep the exemption
narrow.
Existing 23 chat integration tests still pass; SignalR integration tests added
in the next commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tests via in-memory HubConnection (long-polling transport since the TestServer has no WebSocket support): - ChatMessageCreated lands on the channel group when a message is sent - ChatMessageEdited mirrors edits across the channel - ChatMessageDeleted mirrors deletes across the channel - ChatChannelRead lands on the user's own group (cross-tab badge clear) - Hub rejects unauthenticated connections Also fixes AppHub authentication: ICurrentUser depends on IHttpContextAccessor, which doesn't pin the originating negotiate HttpContext to subsequent hub invocations. Switched to reading the user id directly off Context.User (NameIdentifier / sub / uid) which is the canonical SignalR pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gration Peer module to Chat. Module Order 750 runs ahead of Chat (800) so its integration-event subscribers are registered before Chat publishes — handler registration is order-sensitive in the in-memory event bus. - Modules.Notifications + .Contracts projects, slnx + Api + Migrations refs - NotificationPermissions (Inbox.View / Inbox.MarkRead; both basic) - Notification aggregate (UserId, Type, Title, Body, Link, Source, MetadataJson, ReadAtUtc, CreatedAtUtc) + MarkRead() - NotificationsDbContext on schema "notifications" + EF config with the composite (UserId, ReadAtUtc, CreatedAtUtc) index that covers every inbox query path - AddIntegrationEventHandlers wiring so Slice 3.5's mention handler picks up automatically once written - InitialNotifications EF migration Mediator markers intentionally omitted from Program.cs until Slice 3.3 introduces the first ICommand/IQuery (Mediator source-gen MSG0007). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Under /api/v1/notifications, all scoped to the authenticated caller:
GET / ListNotifications?unreadOnly=&page=&pageSize=
GET /unread-count GetUnreadCount — bell badge
POST /{id}/read MarkNotificationRead — single
POST /read-all MarkAllNotificationsRead — returns count updated
Mark-read commands use a (Id, UserId) filter so callers can only mutate
their own rows (404 instead of 403 on cross-user reach). MarkAll uses
ExecuteUpdateAsync for a single bulk UPDATE — no row materialization.
Also re-enables Notifications markers in Program.cs::AddMediator now
that ICommand/IQuery types exist (was deferred to avoid MSG0007).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a child MessageMention entity attached to Message.Mentions, with position (StartIndex, Length) preserved from the raw body so the UI can highlight without re-parsing. SendMessageCommandHandler now: - extracts @username tokens via MentionParser (Slack/Discord-style regex) - resolves them via IMentionResolver (default: filters Identity user list) - drops unresolved tokens and self-mentions silently - attaches one MessageMention per resolved (other) user - publishes one MentionedInChannelIntegrationEvent per distinct mentioned user (Notifications module subscribes in Slice 3.5) MentionedInChannelIntegrationEvent lives in Modules.Chat.Contracts (the public surface). MentionResolver implementation stays internal to Chat. AddMessageMentions EF migration creates the chat.MessageMentions table with cascade-delete FK to Messages and indexes on MentionedUserId + MessageId. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Notifications subscribes to the Chat module's MentionedInChannelIntegrationEvent:
- Persists a chat.mention Notification row for the mentioned user
- Pushes a NotificationCreated SignalR event to user:{id} so the bell
badge updates live across that user's tabs
Handler lives in Notifications/IntegrationEventHandlers/ (top-level, not
under Features/) so arch tests don't flag the non-versioned folder
under Features — matches the Identity Events/ convention.
Integration tests cover the three meaningful paths:
- @-mention persists a Notification visible in the recipient's inbox
- @-mention fires NotificationCreated over SignalR (long-polling test
transport; receives via HubConnection authenticated as the recipient)
- Self-mentions are dropped (no notification row written)
Tests bypass /register's email-confirmation gate via UserManager so the
second user can sign in immediately — matches the existing
EmailConfirmationTests pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Threads:
- GET /chat/messages/{id}/replies — cursor-paged, reverse chronological
via Guid v7. RequireMember through parent's channel.
- DeleteMessage already decrements parent ReplyCount (Slice 1) — covered
by a new integration test here.
Reactions:
- MessageReaction child entity attached to Message.Reactions; unique
index on (MessageId, UserId, Emoji) so a user can react with the
same emoji only once per message.
- AddReaction/RemoveReaction handlers — idempotent (re-add and absent-
remove are silent no-ops, no spurious broadcasts).
- ChatReactionChanged SignalR event broadcast to channel:{id} on each
mutation with { channelId, messageId, userId, emoji, kind }.
- MessageDto now carries Reactions (forces the dashboard contract to
pick up the new shape).
8 new integration tests + 6 new unit tests; existing 23 chat REST +
5 realtime + 3 mention all still green.
AddMessageReactions EF migration creates chat.MessageReactions with
cascade FK to Messages and the composite unique index.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GET /api/v1/chat/search?q=&channelId=&page=&pageSize= — ranked by ts_rank with a stable Id-desc tiebreaker for deterministic pagination. - AddMessagesFullTextSearch migration: GENERATED ALWAYS AS tsvector column on chat.Messages + GIN index. Kept out of the EF model (raw SQL) since EF doesn't track GENERATED columns and we never write to it from C#. - SearchMessagesQueryHandler issues a single parameterized FromSqlInterpolated query joining against the caller's member channels. Channel-scoping is enforced server-side so non-members cannot leak results from channels they don't belong to. - Uses websearch_to_tsquery so callers can write natural search syntax (quoted phrases, OR, -exclude) without server-side parsing. - Soft-deleted messages (DeletedAtUtc IS NOT NULL) are excluded. 4 integration tests cover: token match in member channel, channelId narrowing, non-member exclusion (registered second user, blank result), and soft-delete tombstone exclusion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two HubConnections sharing a channel: - Typing broadcasts ChatTypingStarted to other channel members - Rapid Typing calls within the 3s window throttle down to a single ChatTypingStarted event (distributed-cache marker) - Non-members never see the typing broadcast Adds an EventInbox.DrainAsync helper that collects every event within a window — needed because the throttle test asserts a *count* of 1 rather than just "at least one matched". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OwnerType="ChatChannel", OwnerId=channelId. Dashboard's RequestUploadUrl flow now authorizes attachments routed through chat: - Attach + Read: caller must be a member of the channel (current membership, ASP.NET-Core-independent check against ChatDbContext) - Delete: uploader-only. Moderators can already remove the whole message via Messages.DeleteAny; cascading delete on Message → Attachment takes care of the row. 3 integration tests verify the policy enforces the membership gate on /api/v1/files/upload-url: - Member sees 200 with a presigned PUT URL - Non-member sees 403 ForbiddenException → ProblemDetails - Missing ownerId on a ChatChannel attach also sees 403 — the policy refuses to attach files that aren't bound to a channel Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Slack-style /chat experience built on the same editorial-console aesthetic
as the rest of the dashboard (Geist + JetBrains Mono, oklch surfaces,
brand-tinted atmospherics). Distinctive moves layered in:
- Channel rail: editorial sidebar with mono uppercase section captions,
unread badges in primary tone, active row gets the dashboard's 2px
brand bar.
- Channel header: atmospheric brand glow tucked in the trailing edge
via .chat-channel-header pseudo-element.
- Message stream: virtualised via @tanstack/react-virtual; chapter-break
day rules in mono uppercase ("TODAY" / "YESTERDAY" / weekday); message
blocks merge for same-author sub-5min runs to reduce visual noise;
caller's own messages get a 2px brand-tinted left edge; @mentions
render as inline chat-mention pills.
- Reaction chips: outlined pill that lights up in primary tones for the
caller's own reactions; quick-pick popover (👍 🎉 ❤️ 👀 🔥 🚀).
- Composer plinth: brand-tinted ring on focus, auto-grow textarea, send
button springs to brand on hover, keyboard hint row in mono.
- Typing indicator: three-dot wave keyframe (fsh-chat-typing) with 160ms
phase offset between dots; fires Typing(channelId) on each keystroke
(hub itself rate-limits per project_signalr_hub_user gotcha).
- NotificationBell: bell with destructive badge count in topbar; opens a
brand-glow dropdown inbox mirroring the profile dropdown's atmospheric
hero panel; live-patches via NotificationCreated SignalR event.
Realtime infrastructure:
- RealtimeProvider opens a singleton @microsoft/signalr HubConnection to
/api/v1/realtime/hub authenticated via accessTokenFactory (no manual
query-string fiddling). Auto-reconnect with [2s, 5s, 10s, 30s] backoff
+ lazy rebuild on hard close so JWT rotation is picked up cleanly.
useRealtimeEvent<T>("EventName", handler) typed hook with ref-based
handler stability so subscribers don't re-bind on every render.
- Mounted alongside SseProvider in AppShell so the chat page and the
notification bell share one connection.
Wires up: nav-data.ts /chat entry, /chat + /chat/:channelId routes,
NotificationBell mounted in the topbar before the profile dropdown.
Also lands the previously-uncommitted dashboard layout refactor in this
branch (sidebar + topbar + mobile-nav + app-shell + several page CSS
tweaks + eslint.config) since Mukesh approved touching them. Those
changes were authored across earlier turns and are not chat-specific —
the chat work just sits cleanly on top.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…, multi-line edit, delete confirm + 6 more 11-item audit-and-polish through the /chat surface and NotificationBell, holding the editorial-console aesthetic (Geist + JetBrains Mono, oklch surfaces, 2px brand bar, atmospheric radial glows). Entirely client-side — no API surface changes. Distinctive moves layered in: - Unread divider: the .chat-unread-divider CSS was already defined but never rendered. Now wired in MessageList between the watermark message and the first unread, with the watermark snapshotted via a ref keyed on channelId so it stays put when mark-read fires. - Jump-to-bottom pill: spring-in keyframe (fsh-chat-jump-in), brand-tinted ring + glow, mono caption, monospace tabular count chip. Only ticks the unseen counter when a message lands AND user is scrolled away from bottom. - Channel search in the rail: mono-placeholder filter input (case-insensitive substring match against channelTitle), clearable via X chip on the right. Footer count flips to "N of M" while filtering. - Live connection state: new RealtimeStatusPill component consumes useRealtime().status and renders LIVE/RECONNECTING/OFFLINE with a colored dot. Reconnecting dot pulses (fsh-chat-status-pulse). Mounted in both the rail footer (replacing static "N channels") and the bell dropdown footer (replacing static "Live · realtime"). Uses --color-success / --color-warning design tokens so it themes correctly. - Elevated EmptyState: atmospheric brand-glow hero panel with dotted texture overlay, mono uppercase eyebrow caption, display-typography heading, and three kbd hint chips (↵ Send · ⇧↵ Newline · @ Mention) for affordance. - Multi-line edit: replaced the single-line Input with an auto-growing textarea matching the composer (6-line cap, useLayoutEffect grow, caret parked at end on mount, ↵=save / ⇧↵=newline / Esc=cancel). - Delete confirmation: routes the trash button through a Dialog with a brand-bordered blockquote preview of the message body (truncated at 140 chars). Sonner toast on success / failure. - Mention pills are now <button>: interactive, keyboard-focusable, hover state via button.chat-mention CSS rule, click copies "@username" to clipboard with a sonner toast. - New DM affordance: Plus button on the Direct Messages section opens a NewDmDialog backed by searchUsers (250ms debounce, min 2 chars). Picker shows avatar + display name + email. Selection fires findOrCreateDm and navigates to the resulting channel. - Honest composer hint: "Type @username to mention" replaces the misleading "@ Mention" copy (no autocomplete is shipped — literal @ typing still becomes a mention notification server-side). Verification: - npx tsc -b → 0 errors - npx vite build → 0 errors, 2.92s. Chat ships as a 51.86 KB / 14.70 KB gzip chunk. - npx eslint on chat / notifications / realtime → 0 errors, 0 new warnings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
….Middleware.Tests)
… audit Findings and fixes from a race-condition / code-smell audit (Roslyn navigator detect_antipatterns, find_dead_code, detect_circular_dependencies + targeted concurrency sweeps). Concurrency hygiene was already strong — no async void, no sync-over-async, no fire-and-forget, thread-safe singletons, zero circular deps. The genuine items: - Audit (static): swap the enricher list for an atomically-replaced immutable array read once per WriteAsync. Kills the latent "collection modified" race if Configure() ever runs concurrently with enrichment, and removes a process-global mutable-List test-isolation hazard. - QuotaEnforcementMiddleware: inject TimeProvider instead of DateTimeOffset.UtcNow for Retry-After, matching the rest of the Quota subsystem (deterministic in tests). - PresenceTracker.Connect: also report the offline->online transition when the AddOrUpdate update factory resurrects a count==0 key (Disconnect set 0 but hadn't removed it yet) — previously a reconnect in that window missed the presence broadcast. - ChannelMember: remove dead SetMuted (zero callers); IsMuted is now get-only (still persisted + exposed via DTO, populated by EF via backing field). Investigated but intentionally NOT changed (false positives confirmed): - catch(Exception) in hosted services already filter OperationCanceledException. - Flagged EF "missing AsNoTracking" queries all read-then-mutate-then-save (tracking required); the read-only paths already use AsNoTracking. - find_dead_code hits for ChannelAuthorization / EntityEntryExtensions are used extension methods; EF model snapshots are tooling artifacts. All unit (779) + integration (665) + middleware (5) tests green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… (accurate to current codebase) Establish AGENTS.md as the canonical, tool-neutral AI guide (CLAUDE.md/GEMINI.md are thin @import bridges) and build out .agents/ guidance verified against the current code via Roslyn navigation + real-file extraction. Rules (.agents/rules/) — lean, on-demand, indexed from AGENTS.md: - cross-cutting: architecture, api-conventions, database, eventing, caching, jobs, resilience, storage, security, realtime, logging, testing, integration-testing - per-module: identity, multitenancy, chat, files, webhooks, auditing, billing, catalog, tickets, notifications - frontend: shared + admin + dashboard (the two React apps' real divergences) - replaces the old flat modules.md/persistence.md/testing-rules.md Skills (.agents/skills/) — audited the 6 existing against the live codebase and rewrote them (they were stale: IRepository<T>, PagedList<T>, Moq/FluentAssertions, Guid.NewGuid, class-level [FshModule(Order=n)], DbContext vs BaseDbContext, only 2 of the 4 module-registration sites). Added 5: add-react-page, add-full-slice, create-migration, add-integration-event, add-permission. Skills hold the recipe. Workflows (.agents/workflows/) — reconciled so they orchestrate and delegate to skills (no duplicated/contradictory templates); fixed stale facts (migrations run via DbMigrator, not on startup; TenantDbContext not "MultitenancyDbContext"). Also corrects the [FshModule] attribute (assembly-level positional, not class-level) in architecture.md and the AGENTS.md pointer. Docs only; no source/build impact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop nswag.consolecore from dotnet-tools and delete scripts/openapi/* (NSwag-based C# client generation is no longer used). - Remove scripts/test-cli.ps1 and the requirements/frontend-and-platform.md planning doc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…page redesign, v10 - AI-Driven Development docs section (overview, AGENTS.md & .agents/, skills & workflows, developing with Claude Code) + section registration. - Sponsorship: sticky sponsor card atop the docs ToC, and a "Back the build" home section (Open Collective), placed right under the architecture section. - Homepage: new "Stack at a glance" bento opener; the architecture section rebuilt as a modular-monolith + vertical-slice diagram (two balanced boards); FSH brand mark in the hero; "Who it's for" redesigned as icon-led cards; module-card code panels removed; competitor product references removed. - Version: product version aligned to v10 (.NET 10) across the homepage and docs copy. API version paths (/api/v1, Features/v1, Contracts.v1) untouched. - Fix: type the header nav `match` field — astro check now 0 errors / 0 warnings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lish - Add "Tested to production standard" landing section (Testing.astro): a four-pillar scoreboard (unit / integration / E2E / architecture) over a green test-run terminal, leading with real-PostgreSQL-via-Testcontainers. Wire in as section 05; renumber WhoItsFor/FAQ/FinalCta to 06/07/08. Sync stale test counts in Hero (900+ -> 1,400+) and the FAQ answer. - Theme toggle: replace the muddy CSS token cross-fade with a View Transitions API snapshot cross-fade (GPU-composited, text stays crisp, no frame drops); clean instant fallback for reduced-motion / unsupported browsers. - Mobile responsiveness pass on the homepage: drop heading bases (sections 30px, hero 34px) so they stop dominating phones; hero stats, TechStack, and the testing scoreboard go single-column on mobile; hero terminal scrolls instead of clipping; FAQ answers reclaim full width on mobile. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t, React SPA hosting - Pin Terraform >= 1.15.4 and AWS provider ~> 6.46 in the roots; permissive floors (>= 1.15 / >= 6.0) in child modules. Commit a multi-platform .terraform.lock.hcl locked to aws 6.46.0. - Collapse the shared/ -> app_stack/ wrapper into a single root (provider + backend now live in app_stack/), removing ~500 lines of duplicated vars and outputs plus a passthrough bug that silently dropped ~30 tunables. Dedupe tags via provider default_tags. - Add reusable modules/static_site (private S3 + OAC CloudFront, SPA 403/404->index.html fallback, default-on managed security headers, Terraform-owned config.json) and host the two React SPAs (admin, dashboard) that replaced the removed server-rendered Blazor service. SPA CloudFront origins are auto-added to the API CORS allow-list. No Route53/domain resources (custom aliases optional). - Drop the unnecessary api_task_secrets IAM policy (least privilege). - Add one-command deploy.sh / deploy.ps1: terraform apply -> optional API image build/push -> build + s3 sync + CloudFront invalidate for both SPAs. - Refresh README; terraform fmt + validate clean against aws 6.46.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…laude Pre-existing working-tree changes outside the Terraform audit, committed so the branch is clean: - templates/ NuGet pack csproj, .template.config/template.json, README-template - CLI NewCommand updates; AppHost + solution (slnx) wiring - CI: add template-smoke workflow, ci.yml tweak - docs: site.ts, breadcrumb helper, llms.txt/robots.txt, Cloudflare _headers/_redirects, error-handling page - .gitignore: exclude local .claude worktrees, scheduled_tasks.lock, last30days.env Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ex pages "Overview"
- sidebar: detect the Astro 6 glob-loader bare-dir index id ('frontend' as well
as 'frontend/index') so every section's overview is pulled out and rendered
first, instead of leaking into the page list and sorting by order.
- breadcrumbs: a section overview now renders 'Docs / <Section>' (section is the
current page) instead of duplicating it ('Docs / Modules / Modules'); fixed in
both the visible component and the JSON-LD schema.
- name all 15 category index pages 'Overview' (with descriptive per-page seo.title).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…; rewrite install paths - deployment/aspire: services that spin up locally + the design decisions. - deployment/database-migrations: the DbMigrator (commands, multitenancy, prod usage). - deployment/aws-terraform: end-to-end AWS deploy from prerequisites to one command. - deployment/ci-cd: the GitHub Actions pipeline + template smoke test. - getting-started/install: four install paths with screenshot placeholders. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Delete the Blazor.UI building-block page and drop Blazor.UI / FSH.Starter.Blazor from the project trees, the building-blocks list (llms.txt), and the CORS CSP note. Competitor comparisons (BlazorPlate, ABP) and legacy blazor-* redirects are kept intentionally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- deep-dive: add the subclass 'base.OnModelCreating LAST' rule, the documented Billing isolation exception (plain DbContext + manual TenantId), per-tenant migration ordering, and the AsyncLocal test gotcha. - fix multitenancy module links that pointed at the section index instead of the deep-dive; cross-link Billing to the documented exception. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dingBlocks tests (#1244) * fix(tests): make BuildingBlocks dependency checks cross-platform ProjectReference Include paths are authored with Windows separators (..\Core\Core.csproj). Path.GetFileNameWithoutExtension only treats "\" as a separator on Windows, so on Linux CI it returned the full path minus extension (..\Core\Core) instead of the bare name (Core). This caused BuildingBlocks_Should_Follow_Layered_Dependencies to report 12 false-positive violations on Linux (passed locally on Windows), and silently disabled the module-reference enforcement in BuildingBlocks_Projects_Should_Not_Reference_Modules_Directly. Normalize "\" to "/" before extracting the project name so both checks work on every platform. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(webhooks): require http(s) scheme for subscription URL Uri.TryCreate(UriKind.Absolute) accepts a leading-/ path as an implicit file:// URI on Unix (but not Windows), so "/relative/path" passed validation on the Linux CI runner while the test passed locally on Windows. A webhook target must be an HTTP(S) endpoint regardless of platform, so assert the scheme explicitly. Fixes the Linux-only failure of Create_Should_Fail_When_Url_Relative. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Loads gtag.js site-wide via the base layout head. Both script tags use is:inline so Astro emits them verbatim instead of bundling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chore: extract docs site to a separate repo (fullstackhero/docs)
The Astro docs site now lives at github.com/fullstackhero/docs. Remove
docs/ from this repo and repoint the references that pointed into it.
- Move docs/superpowers/ (audits, specs, plans -- whole-project notes,
not docs-site content) to repo root superpowers/, preserving history
- Remove the docs/ row from the AGENTS.md repo map
- Drop docs/{node_modules,dist,.astro} entries from .gitignore
- Repoint the README "deeper story" link to the new repo
- Drop "docs" from the code-reviewer workflow change-area grouping
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
fix(docker): create MinIO bucket in prod compose + add migrator to root compose deploy/docker (prod): nothing created the S3 bucket the Files module writes to (S3StorageService just PutObjects into Storage:S3:Bucket and never creates it), so the first upload failed with NoSuchBucket. Add a minio-init one-shot (mc mb local/fsh) and gate api on its completion. No anonymous policy -- objects are served via the API / presigned URLs. docker-compose.yml (root/dev): had no migrator, so the API ran against an empty schema (the DB is not migrated at API startup). Add a migrator (apply --seed) + matching JWT/seed env so the seeded admin can log in, gate api on it, and add a header clarifying this is the dev compose (Aspire and deploy/docker are the other paths). DEV-ONLY secrets. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> @
Valkey 8 (BSD-3, the Linux Foundation Redis fork) replaces the
source-available Redis image across Aspire and both compose files. Drop-in:
the kit uses only StackExchange.Redis over RESP (cache, data protection,
SignalR backplane, quota) with no Redis Stack modules, and Hangfire is on
Postgres. Resource/service name stays "redis" so connection strings and
config keys do not churn.
- AppHost: AddRedis(...).WithImage("valkey/valkey","8") + .WithRedisInsight()
(auto-wired cache browser; SSPL but dev-only). The valkey image ships
redis-* symlinks and supports Aspire's TLS command, so the Redis
integration works unmodified.
- docker-compose.yml (dev): valkey/valkey:8-alpine.
- deploy/docker (prod): valkey/valkey:8-alpine + valkey-server/valkey-cli.
Verified by booting Aspire: Valkey starts clean (tcp+tls listeners),
authenticated PING returns PONG, RedisInsight comes up auto-connected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… Valkey Switch the Testcontainers image from redis:7-alpine to valkey/valkey:8-alpine so the suite proves the cache round-trip works on the new engine. Verified locally: 4 passed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add golden rule #10 — a user-facing change (feature, endpoint, config, infra, breaking change) isn't done until the separate docs repo (github.com/fullstackhero/docs) is updated to match and a changelog entry is added. Also fix the stale "Redis 6379" ports line to "Valkey 6379". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(web): return 400 (not 500) for malformed requests / missing required params Anonymous, tenant-scoped endpoints (forgot-password, reset-password, self-register) bind a required `tenant` header. When it is missing, ASP.NET Core throws BadHttpRequestException (StatusCode 400) during parameter binding. GlobalExceptionHandler did not recognise that type and rendered it as a generic 500. Map BadHttpRequestException to its own StatusCode so missing required header/route/query params (and unreadable/oversized bodies) surface as a proper 400 (or 413, etc.) with a ProblemDetails body. The fix applies to every endpoint with a required bound parameter, not just identity. Mirror the mapping in the test-only DetailedTestExceptionHandler, add integration regression tests for the three identity endpoints, and add GlobalExceptionHandler unit tests. Closes #1245 @ Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audit-driven fixes across the tenant dashboard. No backend or BuildingBlocks changes; all verified (tsc clean, eslint 22->12 warnings, build OK, Playwright 121/121). Functional bugs: - realtime-context: wire 4 subscribed-but-unregistered hub events (PresenceChanged, ChatMessagePinned/Unpinned, ChatChannelMemberRead) so live presence/pin/read-receipt updates actually arrive. - chat-search: drop a stateful /g RegExp.test() whose lastIndex drifted and mis-highlighted matches. - user-detail: key the staged-roles reset on userId (not the data array) so a background refetch can no longer wipe unsaved edits. Accessibility: - Combobox: replace invalid role="option" in a menu / role="combobox" without aria-expanded with valid menuitemradio; lift the nested interactive clear button out to a sibling. - accessible names for reaction chips, quick-react emoji, EntitySearch, collapsed sidebar nav items. - role="status"/sr-only on skeletons, role="progressbar" on upload bars, DialogTitle on the mobile nav Sheet, anchor-as-menuitem for link items, h1->h2 heading hierarchy, status rows hidden from the chat log region. - raise placeholder contrast to AA (drop sub-AA alpha multipliers). UX/correctness: - retire hardcoded text-white/bg-white/bg-black via a new --color-overlay-foreground/--color-overlay token + semantic foregrounds. - profile form: surface load errors, gate inputs while loading, seed once. - wire the previously no-op "Reduce motion" toggle through the theme provider; fix the notifications "open bell" selector. Cleanup: - remove 3 `void X` dead-import hacks + a dead effect, swap a full-page window.location reload for router navigate(), and resolve all 10 exhaustive-deps lint warnings via useMemo. Tests: update 3 identity empty-state heading-level assertions to match the corrected h2 hierarchy. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* @ chore(docker): remove redundant root docker-compose.yml Local dev is covered by Aspire (FSH.Starter.AppHost) and production by deploy/docker/docker-compose.yml. The root quick-run compose duplicated infra config with no unique role; nothing references it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> @ * fix(admin): UX/a11y/perf/correctness pass Audit-driven fixes across the operator console. No backend or BuildingBlocks changes; all verified (tsc clean, eslint 2 errors -> 0, build OK, Playwright 93/93). Build-red + real bugs: - fix 2 lint errors: drop the unused `grant` prop on RowActions (active-grants-card) and the dangling `react/no-danger` disable directive that referenced an uninstalled rule (security). - App.tsx: wrap RouterProvider in a top-level Suspense so the public lazy routes (login, password reset, confirm-email) have a boundary on cold chunk fetch instead of throwing. Accessibility (admin had no eslint-plugin-jsx-a11y — now added): - Field primitive now threads aria-describedby + aria-invalid to its control, so every RHF form announces hints/errors (one fix, all forms). - notification-bell: drop the focusable aria-hidden click-away (tabIndex=-1), remove the invalid role="menu", add Escape-to-close, and stop redefining a component inside render. - skip-to-content link + <main id> landmark in AppShell. - accessible names on unlabelled search/filter inputs (users, audits x3, impersonate) and the icon-only webhook delete button. - Segmented toggle gets role="group" + aria-pressed; impersonation details disclosure gets aria-expanded. - role="alert" on login/users/tenants inline errors; role="status" on loaders; reduced-motion now also stops Tailwind's animate-spin. Performance: - impersonation list: collapse two overlapping 5s take:200 polls into one fetch + client-side filtering/counts. - notification-bell: coalesce the per-event invalidation burst. - security: lazy-import the ~50KB qrcode lib only at 2FA enrollment. Correctness: - sessions revoke: track in-flight ids in a Set so concurrent revokes don't clear each other's busy state (user-sessions-card + settings). - invoices row: real <button> inside the <li> instead of a noninteractive <li role="button">. - mobile-nav: capture the trigger node in the effect (ref-in-cleanup). Tooling: add eslint-plugin-jsx-a11y (recommended) with no-autofocus off and label-has-associated-control depth:3; lint passes clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * @ ci: split backend/frontend pipelines, pin SDK, dedupe test load Replace the monolithic ci.yml with path-scoped backend.yml + frontend.yml so a client-only change never builds/tests the API and vice versa. - backend.yml: unit + integration each run ONCE with coverage collection; the coverage job now merges those reports instead of re-running the whole solution (the old double-run was the bulk of the load). Vuln scan gates on direct vulnerable packages. Drops the fragile bin/obj artifact hand-off. - frontend.yml: lint + tsc/vite build + Playwright E2E, matrixed over admin and dashboard (Node 22, npm-cached) — the frontend had no CI before. - global.json pins the .NET 10 GA SDK; all workflows use global-json-file and drop dotnet-quality: preview. Excluded from the template so scaffolded consumer projects are unaffected. - Always-running "Backend CI" / "Frontend CI" gate jobs report green when their side is skipped, so required status checks resolve on cross-cutting PRs. NOTE: branch protection must require the new "Backend CI"/"Frontend CI" checks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> @ --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#Architecture
scripts/openapi/generate-api-clients.ps1 -SpecUrl "<spec>"); Blazor consumes generated clients.